﻿// <copyright file="DeepZoomInitializer.cs" company="Microsoft Corporation">
// Copyright (c) 2009 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2010-05-12</date>
// <summary>DeepZoomInitializer from the Deep Zoom Composer project template</summary>

namespace DemoDeepZoom
{
    using System;
    using System.Windows;
    using System.Windows.Browser;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Interactivity;

    /// <summary>
    /// MultiScaleImage Zoom and Pan behavior
    /// </summary>
    public class DeepZoomInitializer : Behavior<MultiScaleImage>
    {
        /// <summary>
        /// the zoom factor
        /// </summary>
        private double zoom = 1;

        /// <summary>
        /// Is this during a drag
        /// </summary>
        private bool duringDrag = false;

        /// <summary>
        /// Is the mouse down
        /// </summary>
        private bool mouseDown = false;

        /// <summary>
        /// the last mouse down position
        /// </summary>
        private Point lastMouseDownPos = new Point();

        /// <summary>
        /// the last mouse position
        /// </summary>
        private Point lastMousePos = new Point();

        /// <summary>
        /// the last mouse viewport
        /// </summary>
        private Point lastMouseViewPort = new Point();

        /// <summary>
        /// The MultiScaleImage
        /// </summary>
        private MultiScaleImage msi;

        /// <summary>
        /// Gets or sets the zoom factor (default is 1.0)
        /// </summary>
        public double ZoomFactor
        {
            get { return this.zoom; }
            set { this.zoom = value; }
        }

        /// <summary>
        /// unused functions that show the inner math of Deep Zoom
        /// </summary>
        /// <returns>the image rectnagle</returns>
        public Rect GetImageRect()
        {
            return new Rect(-this.msi.ViewportOrigin.X / this.msi.ViewportWidth, -this.msi.ViewportOrigin.Y / this.msi.ViewportWidth, 1 / this.msi.ViewportWidth, 1 / this.msi.ViewportWidth * this.msi.AspectRatio);
        }

        /// <summary>
        /// Get the Zoom About Point Rectangle
        /// </summary>
        /// <param name="img">the image rectangle</param>
        /// <param name="amount">the z amount</param>
        /// <param name="pt">the center point</param>
        /// <returns>the new rectangle</returns>
        public Rect ZoomAboutPoint(Rect img, double amount, Point pt)
        {
            return new Rect(pt.X + ((img.X - pt.X) / amount), pt.Y + ((img.Y - pt.Y) / amount), img.Width / amount, img.Height / amount);
        }

        /// <summary>
        /// Layout the Deep Zoom Interface
        /// </summary>
        /// <param name="rect">the rectangle</param>
        public void LayoutDZI(Rect rect)
        {
            double ar = this.msi.AspectRatio;
            this.msi.ViewportWidth = 1 / rect.Width;
            this.msi.ViewportOrigin = new Point(-rect.Left / rect.Width, -rect.Top / rect.Width);
        }

        /// <summary>
        /// Set the multiscale image and attach the event handlers
        /// </summary>
        protected override void OnAttached()
        {
            this.msi = this.AssociatedObject;

            // Firing an event when the MultiScaleImage is Loaded
            this.msi.Loaded += new RoutedEventHandler(this.OnMsiLoaded);

            // Firing an event when all of the images have been Loaded
            this.msi.ImageOpenSucceeded += new RoutedEventHandler(this.OnMsiImageOpenSucceeded);

            // Handling all of the mouse and keyboard functionality
            this.msi.MouseMove += delegate(object sender, MouseEventArgs e)
            {
                this.lastMousePos = e.GetPosition(this.msi);

                if (this.duringDrag)
                {
                    Point newPoint = this.lastMouseViewPort;
                    newPoint.X += (this.lastMouseDownPos.X - this.lastMousePos.X) / this.msi.ActualWidth * this.msi.ViewportWidth;
                    newPoint.Y += (this.lastMouseDownPos.Y - this.lastMousePos.Y) / this.msi.ActualWidth * this.msi.ViewportWidth;
                    this.msi.ViewportOrigin = newPoint;
                }
            };

            this.msi.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
            {
                this.lastMouseDownPos = e.GetPosition(this.msi);
                this.lastMouseViewPort = this.msi.ViewportOrigin;

                this.mouseDown = true;

                this.msi.CaptureMouse();
            };

            this.msi.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e)
            {
                if (!this.duringDrag)
                {
                    bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
                    double newzoom = this.zoom;

                    if (shiftDown)
                    {
                        newzoom /= 2;
                    }
                    else
                    {
                        newzoom *= 2;
                    }

                    this.Zoom(newzoom, this.msi.ElementToLogicalPoint(this.lastMousePos));
                }

                this.duringDrag = false;
                this.mouseDown = false;

                this.msi.ReleaseMouseCapture();
            };

            this.msi.MouseMove += delegate(object sender, MouseEventArgs e)
            {
                this.lastMousePos = e.GetPosition(this.msi);
                if (this.mouseDown && !this.duringDrag)
                {
                    this.duringDrag = true;
                    double w = this.msi.ViewportWidth;
                    Point o = new Point(this.msi.ViewportOrigin.X, this.msi.ViewportOrigin.Y);
                    this.msi.UseSprings = false;
                    this.msi.ViewportOrigin = new Point(o.X, o.Y);
                    this.msi.ViewportWidth = w;
                    this.zoom = 1 / w;
                    this.msi.UseSprings = true;
                }

                if (this.duringDrag)
                {
                    Point newPoint = this.lastMouseViewPort;
                    newPoint.X += (this.lastMouseDownPos.X - this.lastMousePos.X) / this.msi.ActualWidth * this.msi.ViewportWidth;
                    newPoint.Y += (this.lastMouseDownPos.Y - this.lastMousePos.Y) / this.msi.ActualWidth * this.msi.ViewportWidth;
                    this.msi.ViewportOrigin = newPoint;
                }
            };

            new MouseWheelHelper(this.msi).Moved += delegate(object sender, MouseWheelEventArgs e)
            {
                e.Handled = true;

                double newzoom = this.zoom;

                if (e.Delta < 0)
                {
                    newzoom /= 1.3;
                }
                else
                {
                    newzoom *= 1.3;
                }

                this.Zoom(newzoom, this.msi.ElementToLogicalPoint(this.lastMousePos));

                this.msi.CaptureMouse();
            };
        }

        /// <summary>
        /// Set the viewport width
        /// </summary>
        /// <param name="sender">the MultiScaleImage</param>
        /// <param name="e">the routed event arguments</param>
        private void OnMsiImageOpenSucceeded(object sender, RoutedEventArgs e)
        {
            // If collection, this gets you a list of all of the MultiScaleSubImages
            ////foreach (MultiScaleSubImage subImage in msi.SubImages)
            ////{
            ////    // Do something
            ////}

            this.msi.ViewportWidth = 1;
        }

        /// <summary>
        /// Loaded event handler
        /// </summary>
        /// <param name="sender">the MultiScaleImage</param>
        /// <param name="e">the routed event arguments</param>
        private void OnMsiLoaded(object sender, RoutedEventArgs e)
        {
            // Hook up any events you want when the image has successfully been opened
        }

        /// <summary>
        /// Zoom to a specific location/factor
        /// </summary>
        /// <param name="newzoom">the new zoom factor</param>
        /// <param name="p">the point to zoom to</param>
        private void Zoom(double newzoom, Point p)
        {
            if (newzoom < 0.5)
            {
                newzoom = 0.5;
            }

            this.msi.ZoomAboutLogicalPoint(newzoom / this.zoom, p.X, p.Y);
            this.zoom = newzoom;
        }

        /// <summary>
        /// Zoom in the MSI
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="e">the routed event arguments</param>
        private void ZoomInClick(object sender, System.Windows.RoutedEventArgs e)
        {
            this.Zoom(this.zoom * 1.3, this.msi.ElementToLogicalPoint(new Point(.5 * this.msi.ActualWidth, .5 * this.msi.ActualHeight)));
        }

        /// <summary>
        /// Zoom out the MSI
        /// </summary>
        /// <param name="sender">the sending object</param>
        /// <param name="e">the routed event arguments</param>
        private void ZoomOutClick(object sender, System.Windows.RoutedEventArgs e)
        {
            this.Zoom(this.zoom / 1.3, this.msi.ElementToLogicalPoint(new Point(.5 * this.msi.ActualWidth, .5 * this.msi.ActualHeight)));
        }

        /// <summary>
        /// Go to the home view
        /// </summary>
        /// <param name="sender">the sending object</param>
        /// <param name="e">the routed event arguments</param>
        private void GoHomeClick(object sender, System.Windows.RoutedEventArgs e)
        {
            this.msi.ViewportWidth = 1;
            this.msi.ViewportOrigin = new Point(0, 0);
            this.ZoomFactor = 1;
        }

        /// <summary>
        /// Go full screen
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="e">the routed event arguments</param>
        private void GoFullScreenClick(object sender, System.Windows.RoutedEventArgs e)
        {
            if (!Application.Current.Host.Content.IsFullScreen) 
            { 
                Application.Current.Host.Content.IsFullScreen = true; 
            } 
            else 
            { 
                Application.Current.Host.Content.IsFullScreen = false; 
            } 
        }

        /// <summary>
        /// Mouse wheel event arguments
        /// </summary>
        public class MouseWheelEventArgs : EventArgs
        {
            /// <summary>
            /// the mouse wheel delta
            /// </summary>
            private double delta;

            /// <summary>
            /// was the event handled
            /// </summary>
            private bool handled = false;

            /// <summary>
            /// Initializes a new instance of the MouseWheelEventArgs class
            /// </summary>
            /// <param name="delta">the wheel delta</param>
            public MouseWheelEventArgs(double delta)
            {
                this.delta = delta;
            }

            /// <summary>
            /// Gets the delta
            /// </summary>
            public double Delta
            {
                get { return this.delta; }
            }

            /// <summary>
            /// Gets or sets a value indicating whether the browser handled the event
            /// </summary>
            /// <remarks>Use handled to prevent the default browser behavior!</remarks>
            public bool Handled
            {
                get { return this.handled; }
                set { this.handled = value; }
            }
        }

        /// <summary>
        /// Mouse Wheel Helper
        /// </summary>
        public class MouseWheelHelper
        {
            /// <summary>
            /// The worker
            /// </summary>
            private static Worker worker;

            /// <summary>
            /// Is the mouse over?
            /// </summary>
            private bool isMouseOver = false;

            /// <summary>
            /// Initializes a new instance of the MouseWheelHelper class
            /// </summary>
            /// <param name="element">the framework element</param>
            public MouseWheelHelper(FrameworkElement element)
            {
                if (MouseWheelHelper.worker == null)
                {
                    MouseWheelHelper.worker = new Worker();
                }

                MouseWheelHelper.worker.Moved += this.HandleMouseWheel;

                element.MouseEnter += this.HandleMouseEnter;
                element.MouseLeave += this.HandleMouseLeave;
                element.MouseMove += this.HandleMouseMove;
            }

            /// <summary>
            /// the mouse wheel Moved event
            /// </summary>
            public event EventHandler<MouseWheelEventArgs> Moved;

            /// <summary>
            /// Handle a mouse wheel event
            /// </summary>
            /// <param name="sender">the HTML Window or Document</param>
            /// <param name="args">the mouse wheel event arguments</param>
            private void HandleMouseWheel(object sender, MouseWheelEventArgs args)
            {
                if (this.isMouseOver)
                {
                    this.Moved(this, args);
                }
            }

            /// <summary>
            /// Handle mouse enter
            /// </summary>
            /// <param name="sender">the framework element</param>
            /// <param name="e">the event arguments</param>
            private void HandleMouseEnter(object sender, EventArgs e)
            {
                this.isMouseOver = true;
            }

            /// <summary>
            /// Handle a mouse leave event
            /// </summary>
            /// <param name="sender">the framework element</param>
            /// <param name="e">the event arguments</param>
            private void HandleMouseLeave(object sender, EventArgs e)
            {
                this.isMouseOver = false;
            }

            /// <summary>
            /// Handle a mouse move event
            /// </summary>
            /// <param name="sender">the framework element</param>
            /// <param name="e">the event argumetns</param>
            private void HandleMouseMove(object sender, EventArgs e)
            {
                this.isMouseOver = true;
            }

            /// <summary>
            /// Worker class
            /// </summary>
            private class Worker
            {
                /// <summary>
                /// Initializes a new instance of the Worker class.
                /// </summary>
                public Worker()
                {
                    if (HtmlPage.IsEnabled)
                    {
                        HtmlPage.Window.AttachEvent("DOMMouseScroll", this.HandleMouseWheel);
                        HtmlPage.Window.AttachEvent("onmousewheel", this.HandleMouseWheel);
                        HtmlPage.Document.AttachEvent("onmousewheel", this.HandleMouseWheel);
                    }
                }

                /// <summary>
                /// Moved event
                /// </summary>
                public event EventHandler<MouseWheelEventArgs> Moved;

                /// <summary>
                /// Handle a mouse wheel event
                /// </summary>
                /// <param name="sender">the HTML Window or Document</param>
                /// <param name="args">the HTML Event arguments</param>
                private void HandleMouseWheel(object sender, HtmlEventArgs args)
                {
                    double delta = 0;

                    ScriptObject eventObj = args.EventObject;

                    if (eventObj.GetProperty("wheelDelta") != null)
                    {
                        delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;

                        if (HtmlPage.Window.GetProperty("opera") != null)
                        {
                            delta = -delta;
                        }
                    }
                    else if (eventObj.GetProperty("detail") != null)
                    {
                        delta = -((double)eventObj.GetProperty("detail")) / 3;

                        if (HtmlPage.BrowserInformation.UserAgent.IndexOf("Macintosh") != -1)
                        {
                            delta = delta * 3;
                        }
                    }

                    if (delta != 0 && this.Moved != null)
                    {
                        MouseWheelEventArgs wheelArgs = new MouseWheelEventArgs(delta);
                        this.Moved(this, wheelArgs);

                        if (wheelArgs.Handled)
                        {
                            args.PreventDefault();
                        }
                    }
                }
            }
        }
    }
}
